﻿namespace Pyramid
{
    using System;
    using System.Diagnostics;

    /// <summary>
    /// Represents a digit in balanced ternary or the base unit of ternary
    /// logic.
    /// </summary>
    public struct Trivalent
        : IConvertible, IEquatable<Trivalent>, IComparable<Trivalent>
    {
        #region Private Constant Values

        /// <summary>
        /// The internal store of a Trivalent instance representing the True
        /// value.
        /// </summary>
        private static readonly Trivalent _true = new Trivalent(1);

        /// <summary>
        /// The internal store of a Trivalent instance representing the
        /// Unknown value.
        /// </summary>
        private static readonly Trivalent _unknown = new Trivalent(0);

        /// <summary>
        /// The internal store of a Trivalent instance representing the False
        /// value.
        /// </summary>
        private static readonly Trivalent _false = new Trivalent(-1);

        /// <summary>
        /// A culture-invariant string representation of the True value.
        /// </summary>
        private const string TRUE_STRING = "True";

        /// <summary>
        /// A culture-invariant string representation of the Unknown value.
        /// </summary>
        private const string UNKNOWN_STRING = "Unknown";

        /// <summary>
        /// A culture-invariant string representation of the False value.
        /// </summary>
        private const string FALSE_STRING = "False";

        #endregion

        #region Private Data Members

        /// <summary>
        /// An internal store representing the value of this instance.
        /// </summary>
        /// <remarks>
        /// The three accepted values of this member are 1, 0 &amp; -1, in
        /// correspondence with the values of a balanced ternary digit.
        /// </remarks>
        private readonly sbyte value;

        #endregion

        /// <summary>
        /// Initialises a new instance of the Trivalent struct.
        /// </summary>
        /// <param name="value">
        /// The value of the instance.
        /// </param>
        private Trivalent(sbyte value)
        {
            Debug.Assert(Math.Abs(value) < 2);
            this.value = value;
        }

        #region Public Override Methods

        public override bool Equals(object obj)
        {
            return obj is Trivalent && ((Trivalent)obj) == this;
        }

        public override int GetHashCode()
        {
            return this.value;
        }

        public override string ToString()
        {
            switch (this.value)
            {
                case 1:
                    return TrueString;
                case -1:
                    return FalseString;
                default:
                    return UnknownString;
            }
        }

        #endregion

        #region Public Static Operations

        public static Trivalent Not(Trivalent operand)
        {
            return new Trivalent((sbyte)-operand.value);
        }

        // Kleene logic
        // A 	        B 	        A OR B 	    A AND B     A XOR B 	NOT A       A MASK B
        // True 	    True 	    True 	    True        False       False       True
        // True 	    Unknown 	True 	    Unknown     Unknown                 Unknown
        // True 	    False 	    True 	    False       True                    False
        // Unknown 	    True 	    True 	    Unknown     Unknown     Unknown     Unknown
        // Unknown 	    Unknown 	Unknown 	Unknown     Unknown                 Unknown
        // Unknown 	    False 	    Unknown 	False       Unknown                 Unknown
        // False 	    True 	    True 	    False       True        True        False
        // False 	    Unknown 	Unknown 	False       Unknown                 Unknown
        // False 	    False 	    False 	    False       False                   True

        public static Trivalent And(Trivalent operandA, Trivalent operandB)
        {
            if (operandA == False || operandB == False) return False;
            if (operandA == True && operandB == True) return True;
            return Unknown;
        }

        public static Trivalent Or(Trivalent operandA, Trivalent operandB)
        {
            if (operandA == True || operandB == True) return True;
            if (operandA == False && operandB == False) return False;
            return Unknown;
        }

        public static Trivalent Mask(Trivalent operandA, Trivalent operandB)
        {
            if (operandA == Trivalent.True)
            {
                return operandB;
            }
            else if (operandA == Trivalent.Unknown)
            {
                return Trivalent.Unknown;
            }
            else
            {
                return Trivalent.Not(operandB);
            }
        }

        public static Trivalent Xor(Trivalent operandA, Trivalent operandB)
        {
            Trivalent orResult = Or(operandA, operandB);
            if (orResult == True)
            {
                if (operandA == operandB)
                    return False;
                else if (operandA == Not(operandB))
                    return True;
                else
                    return Unknown;
            }
            else
                return orResult;
        }

        public static Trivalent CycleUp(Trivalent operand)
        {
            if (operand == False)
                return Unknown;
            else if (operand == True)
                return False;
            else // (operand == Unknown)
                return True;
        }

        public static Trivalent CycleDown(Trivalent operand)
        {
            if (operand == False)
                return True;
            else if (operand == True)
                return Unknown;
            else // (operand == Unknown)
                return False;
        }

        public static bool TryParse(string s, out Trivalent result)
        {
            switch (s.ToUpperInvariant())
            {
                case "1":
                case "TRUE":
                    result = Trivalent.True;
                    return true;
                case "0":
                case "UNKNOWN":
                    result = Trivalent.Unknown;
                    return true;
                case "Z":
                case "FALSE":
                    result = Trivalent.False;
                    return true;
                default:
                    result = Trivalent.Unknown;
                    return false;
            }
        }

        public static Trivalent Parse(string s)
        {
            Trivalent result;

            if (TryParse(s, out result))
            {
                return result;
            }
            else
            {
                throw new FormatException("Input string was not in a correct format.");
            }
        }

        #endregion

        public static explicit operator int(Trivalent value)
        {
            return value.value;
        }

        public static implicit operator Trivalent(int value)
        {
            return new Trivalent((sbyte)Math.Sign(value));
        }

        public static explicit operator bool(Trivalent value)
        {
            return value == Trivalent.True;
        }

        public static implicit operator Trivalent(bool value)
        {
            return value ? Trivalent.True : Trivalent.False;
        }

        public static bool operator ==(Trivalent leftOperand, Trivalent rightOperand)
        {
            return leftOperand.value == rightOperand.value;
        }

        public static bool operator !=(Trivalent leftOperand, Trivalent rightOperand)
        {
            return !(leftOperand == rightOperand);
        }

        public static Trivalent True
        {
            get
            {
                return _true;
            }
        }

        public static Trivalent Unknown
        {
            get
            {
                return _unknown;
            }
        }

        public static Trivalent False
        {
            get
            {
                return _false;
            }
        }

        public static string TrueString
        {
            get
            {
                return TRUE_STRING;
            }
        }

        public static string UnknownString
        {
            get
            {
                return UNKNOWN_STRING;
            }
        }

        public static string FalseString
        {
            get
            {
                return FALSE_STRING;
            }
        }

        #region IConvertible Members

        public TypeCode GetTypeCode()
        {
            return TypeCode.Object;
        }

        public bool ToBoolean(IFormatProvider provider)
        {
            return this == True;
        }

        public byte ToByte(IFormatProvider provider)
        {
            return (byte)this.value;
        }

        public char ToChar(IFormatProvider provider)
        {
            throw new NotImplementedException();
        }

        public DateTime ToDateTime(IFormatProvider provider)
        {
            throw new InvalidCastException();
        }

        public decimal ToDecimal(IFormatProvider provider)
        {
            return this.value;
        }

        public double ToDouble(IFormatProvider provider)
        {
            return this.value;
        }

        public short ToInt16(IFormatProvider provider)
        {
            return this.value;
        }

        public int ToInt32(IFormatProvider provider)
        {
            return this.value;
        }

        public long ToInt64(IFormatProvider provider)
        {
            return this.value;
        }

        [CLSCompliant(false)]
        public sbyte ToSByte(IFormatProvider provider)
        {
            return this.value;
        }

        public float ToSingle(IFormatProvider provider)
        {
            return this.value;
        }

        public string ToString(IFormatProvider provider)
        {
            return this.ToString();
        }

        public object ToType(Type conversionType, IFormatProvider provider)
        {
            return ((IConvertible)this.value).ToType(conversionType, provider);
        }

        [CLSCompliant(false)]
        public ushort ToUInt16(IFormatProvider provider)
        {
            return (ushort)this.value;
        }

        [CLSCompliant(false)]
        public uint ToUInt32(IFormatProvider provider)
        {
            return (uint)this.value;
        }

        [CLSCompliant(false)]
        public ulong ToUInt64(IFormatProvider provider)
        {
            return (ulong)this.value;
        }

        #endregion

        #region IEquatable<Trivalent> Members

        public bool Equals(Trivalent other)
        {
            return this.value == other.value;
        }

        #endregion

        #region IComparable<Trivalent> Members

        public int CompareTo(Trivalent other)
        {
            return this.value.CompareTo(other.value);
        }

        #endregion
    }
}
